/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.services;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.http.HttpServletRequest;
import javax.annotation.security.RolesAllowed;
import org.directwebremoting.annotations.RemoteProxy;
import org.directwebremoting.annotations.RemoteMethod;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.model.UUID;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.model.Document;
import org.internna.iwebmvc.dao.SecurityDAO;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebjtracker.model.TrackerUser;
import org.internna.iwebjtracker.model.DefaultModule;
import org.internna.iwebjtracker.model.DefaultProject;
import org.internna.iwebjtracker.model.DefaultVersion;
import org.internna.iwebjtracker.server.ServerManager;
import org.internna.iwebjtracker.project.validation.Errors;
import org.internna.iwebjtracker.model.DefaultConnectionDetails;
import org.springframework.context.MessageSource;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Transactional;

import static org.internna.iwebmvc.utils.StringUtils.hasText;
import static org.internna.iwebjtracker.boot.tasks.CreateRolesStartupTask.ROLE_DEVELOPER;
import static org.internna.iwebjtracker.services.AbstractProjectManager.ROLE_PROJECT_MANAGER;

/**
 * Default implementation of an AJAX project manager.
 *
 * @author Jose Noheda
 * @since 1.0
 */
@RemoteProxy(name = "DefaultProjectManager")
public final class DefaultProjectManager extends AbstractProjectManager<DefaultConnectionDetails, DefaultModule, DefaultVersion, TrackerUser, DefaultProject> {

    private static final Log logger = LogFactory.getLog(DefaultProjectManager.class);

    private DAO dao;
    private SecurityDAO securityDAO;
    private TrackerUserManager trackerUserManager;

    /**
     * Inherited constructor. Delegates all logic upwards.
     *
     * @param serverManager any
     * @param messageSource any
     */
    public DefaultProjectManager(ServerManager serverManager, MessageSource messageSource) {
        super(serverManager, messageSource);
    }

    /**
     * Spring (REQUIRED) injection point.
     *
     * @param dao a not null implementation
     */
    @Required public void setDao(DAO dao) {
        Assert.notNull(dao);
        this.dao = dao;
    }

    /**
     * Spring (REQUIRED) injection point.
     *
     * @param securityDAO a not null implementation
     */
    @Required public void setSecurityDAO(SecurityDAO securityDAO) {
        Assert.notNull(securityDAO);
        this.securityDAO = securityDAO;
    }

    /**
     * Spring (REQUIRED) injection point.
     *
     * @param trackerUserManager a not null implementation
     */
    @Required public void setTrackerUserManager(TrackerUserManager trackerUserManager) {
        Assert.notNull(trackerUserManager);
        this.trackerUserManager = trackerUserManager;
    }

    /**
     * Loads the connection details object as long as it has a valid primary key
     * and it has not been loaded before.
     *
     * @param project any
     */
    @Override protected void loadConnectionDetails(DefaultProject project) {
        if ((project != null) && (project.getConnectionDetails() != null) && (project.getConnectionDetails().getId() != null) && (!hasText(project.getConnectionDetails().getHost()))) {
            project.setConnectionDetails(dao.find(DefaultConnectionDetails.class, project.getConnectionDetails().getId()));
        }
    }

    /**
     * Adds the version to the project and proceeds to create it.
     *
     * @param project any
     * @param version any
     * @return
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_PROJECT_MANAGER)
    @Override public DefaultProject createProject(DefaultProject project, String version, HttpServletRequest request) {
        Assert.notNull(request);
        if (project != null) {
            project.createVersion(hasText(version) ? version : "1.0", null);
            project.addDeveloper(trackerUserManager.getCurrentTrackerUser());
        }
        return createProject(project);
    }

    /**
     * Validates the project and proceeds to save it.
     *
     * @param project any project
     * @return the project (modified)
     */
    protected DefaultProject createProject(DefaultProject project) {
        String validation = validateProject(project);
        if (!hasText(validation)) {
            try {
                dao.create(project);
            } catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Project [" + project.getProjectName() + "] could not be persisted: " + ex.getMessage());
                }
            }
        } else if (logger.isDebugEnabled()) {
            logger.debug("Project [" + project.getProjectName() + "] did not validate correctly: " + validation);
        }
        return project;
    }

    /**
     * Validates user and project and adds it to the collection of observers.
     *
     * @param username any
     * @param project any
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed("ROLE_USER")
    @Override public void joinProject(UUID projectId, HttpServletRequest request) {
        TrackerUser trackerUser = trackerUserManager.getCurrentTrackerUser();
        if (trackerUser != null) {
            DefaultProject project = dao.find(DefaultProject.class, projectId);
            if ((project != null) && (project.join(trackerUser))) {
                dao.update(project);
            }
        }
    }

    /**
     * Ensures that the user has the rights to later perform operations.
     *
     * @param project a non null instance
     * @param user a non null instance
     * @param asAdministrator true if the user will become a project developer and administrator
     */
    @Override protected void onAddProjectMember(DefaultProject project, TrackerUser user, boolean asAdministrator) {
        if (user != null) {
            user.addRole(securityDAO.findAuthority(ROLE_DEVELOPER));
            if (asAdministrator) {
                user.addRole(securityDAO.findAuthority(ROLE_PROJECT_MANAGER));
            }
            dao.update(user);
        }
    }

    /**
     * Obtains the logged in user and delegates the logic to the project object.
     *
     * @param project any
     * @param close true if the project should be closed
     * @return any
     */
    @Override protected Errors proceedWithOpenOrClose(DefaultProject project, boolean close) {
        if (project != null) {
            TrackerUser user = trackerUserManager.getCurrentTrackerUser();
            dao.flush();
            Errors error = close ? project.close(user) : project.reopen(user);
            if (error == null) {
                dao.update(project);
            }
            return error;
        }
        return Errors.PROJECT_DOES_NOT_EXIST;
    }

    /**
     * Obtains the logged in user and delegates the logic to the version object.
     *
     * @param version any
     * @return any
     */
    @Override protected Errors proceedWithVersionClose(DefaultVersion version) {
        Errors error = version == null ? Errors.NULL_VERSION : version.close(trackerUserManager.getCurrentTrackerUser());
        if (error == null) {
            dao.update(version);
        }
        return error;
    }

    /**
     * Retrieves the project with that id from database.
     *
     * @param id any
     * @return null if id is null. Otherwise a project
     */
    @Override protected DefaultProject loadProject(UUID id) {
        return id == null ? null : dao.find(DefaultProject.class, id);
    }

    /**
     * Sets the Document loaded from database as the new logo. If the UUID is null
     * it sets the logo as null.
     *
     * @param project any
     * @param logo any
     */
    @Override protected void setProjectLogo(DefaultProject project, UUID logo) {
        if (project != null) {
            project.setLogo(logo == null ? null : dao.find(Document.class, logo));
        }
    }

    /**
     * Updates database information for a project.
     *
     * @param project any
     */
    @Override protected void updateProject(DefaultProject project) {
        if (project != null) {
            dao.update(project);
        }
    }

    /**
     * Loads a DefaultModule entity given its primary key.
     *
     * @param id any
     * @return null if id is null. Otherwise a module.
     */
    @Override protected DefaultModule loadModule(UUID id) {
        return id == null ? null : load(DefaultModule.class, id);
    }

    /**
     * Updates database information for a module.
     *
     * @param module any
     */
    @Override protected void persistModule(DefaultModule module) {
        if (module != null) {
            if (module.getId() == null) {
                dao.create(module);
            } else {
                dao.update(module);
            }
        }
    }

    /**
     * Loads a TrackerUser instance from database.
     *
     * @param id any
     * @return any
     */
    @Override protected TrackerUser loadTrackerUser(UUID id) {
        return id == null ? null : load(TrackerUser.class, id);
    }

    /**
     * Updates database information for a version.
     *
     * @param version any
     */
    @Override protected void persistVersion(DefaultVersion version) {
        if (version != null) {
            if (version.getId() == null) {
                dao.create(version);
            } else {
                dao.update(version);
            }
        }
    }

    /**
     * Loads a DefaultVersion instance from database.
     *
     * @param id any
     * @return any
     */
    @Override protected DefaultVersion loadVersion(UUID id) {
        return id == null ? null : load(DefaultVersion.class, id);
    }

    private <T extends DomainEntity> T load(Class<T> clazz, UUID id) {
        return dao.find(clazz, id);
    }

}
